package Subject;

public class GameOfLife {

/**
 * 难度：中等
 * 
 * 289. 生命游戏
 * 	生命游戏，简称为生命，是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。
 * 	给定一个包含 m × n 个格子的面板，每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态：
 * 	1 即为活细胞（live），或 0 即为死细胞（dead）。每个细胞与其八个相邻位置（水平，垂直，对角线）的细胞都遵循
 * 	以下四条生存定律：
 * 		1.如果活细胞周围八个位置的活细胞数少于两个，则该位置活细胞死亡；
 * 		2.如果活细胞周围八个位置有两个或三个活细胞，则该位置活细胞仍然存活；
 * 		3.如果活细胞周围八个位置有超过三个活细胞，则该位置活细胞死亡；
 * 		4.如果死细胞周围正好有三个活细胞，则该位置死细胞复活；
 * 	根据当前状态，写一个函数来计算面板上所有细胞的下一个（一次更新后的）状态。下一个状态是通过将上述规则同时应用于当
 * 	前状态下的每个细胞所形成的，其中细胞的出生和死亡是同时发生的。
 * 
 * 示例：
 * 	输入： 
 * 	[
 *   [0,1,0],
 *   [0,0,1],
 *   [1,1,1],
 *   [0,0,0]
 * 	]
 * 	输出：
 * 	[
 *   [0,0,0],
 *   [1,0,1],
 *   [0,1,1],
 *   [0,1,0]
 * 	]
 *
 * 进阶：
 * 	你可以使用原地算法解决本题吗？请注意，面板上所有格子需要同时被更新：你不能先更新某些格子，然后使用它们的更新后的
 * 	值再更新其他格子。
 * 	本题中，我们使用二维数组来表示面板。原则上，面板是无限的，但当活细胞侵占了面板边界时会造成问题。你将如何解决这些
 * 	问题？
 *
 * */
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		GameOfLife gol = new GameOfLife();
		int[][] a = {{0,1,0},{0,0,1},{1,1,1},{0,0,0}};
		int[][] b = gol.gameOfLife(a);
		for(int i=0;i<b.length;i++) {
			System.out.println("");
			for(int j=0;j<b[i].length;j++) {
				System.out.print(b[i][j]+",");
			}
		}
	}
	
	public int[][] gameOfLife(int[][] board) {
		/**
        利用一个 two bits 的状态机来记录细胞状态, 第一位表示
        下一状态, 第二位表示当前状态:
        00: dead (next state) <- dead (current state)
        01: dead (next state) <- live (current state) 
        10: live (next state) <- dead (current state)
        11: live (next state) <- live (current state) 
        初始情况对应就是 00 和 01 (默认下一状态是 dead state)
        统计每个位置周围的 live 细胞数决定高位置 1 (live)还是 
        0 (dead), 最后右移一位即为最终状态, 注意不需要考虑 01
        以及 00 的情况, 因为已经默认下一状态为 dead.
        **/
        if(board.length < 1) return null;
        // 更新矩阵
        for(int i = 0; i < board.length; ++i) {
            for(int j = 0; j < board[0].length; ++j) {
                int lives = countLives(board, i, j);
                // live -> live
                if((board[i][j] & 1) == 1) {
                    if(lives >= 2 && lives <= 3)
                        board[i][j] = 3;
                }
                // dead -> live
                else {
                    if(lives == 3)
                        board[i][j] = 2;
                }
            }
        }
        // 重置矩阵
        for(int i = 0; i < board.length; ++i) {
            for(int j = 0; j < board[0].length; ++j) 
                board[i][j] >>= 1;
        }
        return board;
    }
	//计算相邻位置的生存细胞数量
    private int countLives(int[][] b, int i, int j) {
        int count = 0;
        // 相邻位置
        int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}};   
        for(int[] dir : dirs) {
            int x = i + dir[0];
            int y = j + dir[1];
            if(x < 0 || x > b.length-1 || y < 0 || y > b[0].length-1)
                continue;
            count += (b[x][y] & 1);
        }
        return count;
    }
	
    
    //方法一：复制原数组进行模拟(未看)
    public void gameOfLife1(int[][] board) {
        int[] neighbors = {0, 1, -1};

        int rows = board.length;
        int cols = board[0].length;

        // 创建复制数组 copyBoard
        int[][] copyBoard = new int[rows][cols];

        // 从原数组复制一份到 copyBoard 中
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                copyBoard[row][col] = board[row][col];
            }
        }

        // 遍历面板每一个格子里的细胞
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {

                // 对于每一个细胞统计其八个相邻位置里的活细胞数量
                int liveNeighbors = 0;

                for (int i = 0; i < 3; i++) {
                    for (int j = 0; j < 3; j++) {

                        if (!(neighbors[i] == 0 && neighbors[j] == 0)) {
                            int r = (row + neighbors[i]);
                            int c = (col + neighbors[j]);

                            // 查看相邻的细胞是否是活细胞
                            if ((r < rows && r >= 0) && (c < cols && c >= 0) && (copyBoard[r][c] == 1)) {
                                liveNeighbors += 1;
                            }
                        }
                    }
                }

                // 规则 1 或规则 3      
                if ((copyBoard[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) {
                    board[row][col] = 0;
                }
                // 规则 4
                if (copyBoard[row][col] == 0 && liveNeighbors == 3) {
                    board[row][col] = 1;
                }
            }
        }
    }
    //方法二：使用额外的状态(未看)
    public void gameOfLife2(int[][] board) {
        int[] neighbors = {0, 1, -1};

        int rows = board.length;
        int cols = board[0].length;

        // 遍历面板每一个格子里的细胞
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {

                // 对于每一个细胞统计其八个相邻位置里的活细胞数量
                int liveNeighbors = 0;

                for (int i = 0; i < 3; i++) {
                    for (int j = 0; j < 3; j++) {

                        if (!(neighbors[i] == 0 && neighbors[j] == 0)) {
                            // 相邻位置的坐标
                            int r = (row + neighbors[i]);
                            int c = (col + neighbors[j]);

                            // 查看相邻的细胞是否是活细胞
                            if ((r < rows && r >= 0) && (c < cols && c >= 0) && (Math.abs(board[r][c]) == 1)) {
                                liveNeighbors += 1;
                            }
                        }
                    }
                }

                // 规则 1 或规则 3 
                if ((board[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) {
                    // -1 代表这个细胞过去是活的现在死了
                    board[row][col] = -1;
                }
                // 规则 4
                if (board[row][col] == 0 && liveNeighbors == 3) {
                    // 2 代表这个细胞过去是死的现在活了
                    board[row][col] = 2;
                }
            }
        }

        // 遍历 board 得到一次更新后的状态
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                if (board[row][col] > 0) {
                    board[row][col] = 1;
                } else {
                    board[row][col] = 0;
                }
            }
        }
    }
}
